﻿package smooth.core.object.youTubeVideoPlayerAs3Bridge {
	import flash.display.Loader;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.TimerEvent;
	import flash.events.IOErrorEvent;
	import flash.net.*;
	import flash.utils.Timer;
	import flash.system.Security;

	public class YTPlayer extends Sprite {
		public static const UNLOADED:int = -2; // The player SWF is not yet loaded
		public static const UNSTARTED:int = -1;
		public static const ENDED:int = 0;
		public static const PLAYING:int = 1;
		public static const PAUSED:int = 2;
		public static const BUFFERING:int = 3;
		public static const CUED:int = 5;
		// LSA
		public static const FREE:int = 0;
		public static const LOCKED:int = 1;
		
		private static const YTBRIDGE:String = "ytbridge.swf";
		private static var centralInbox:YTBridgeInbox = new YTBridgeInbox();
		private static var pending:Array = new Array();
		// LSA
		private static var _ytPlayerSharedState:int = FREE;
		
		private var inbox:LocalConnection = new LocalConnection();
		private var outbox:LocalConnection = new LocalConnection();
		private var outboxName:String = null;
		private var _volume:int = 100;
		private var _isMuted:Boolean = false;
		private var _playerState:int = UNLOADED;
		private var _monitorPlayback:Boolean = false;
		private var _currentTime:Number = 0;
		private var _timer:Timer = null;
		private var _duration:Number = 0;
		private var _seekTo:int = 0;

		private var developerKey:String = "";
		private var ytBridgeLoader:Loader;
		private var videoId:String;
		
		public function YTPlayer(devKey:String) {
			trace("YTPlayer::YTPlayer::debut");
			this.developerKey = devKey;
			Security.allowDomain('www.youtube.com');
			Security.allowDomain('gdata.youtube.com');
			Security.allowInsecureDomain('gdata.youtube.com');
			Security.allowInsecureDomain('www.youtube.com');
			
			ytBridgeLoader = new Loader();
			ytBridgeLoader.contentLoaderInfo.addEventListener(Event.INIT, onInitialize);	
			ytBridgeLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onLoaderIoError);	
			//if (pending.length == 0) {
			trace("YTPlayer::YTPlayer::_ytPlayerSharedState="+_ytPlayerSharedState);
			if (_ytPlayerSharedState == FREE) {
				_ytPlayerSharedState = LOCKED;
				ytBridgeLoader.load(new URLRequest(YTBRIDGE));
			}
			trace("YTPlayer::YTPlayer::_ytPlayerSharedState="+_ytPlayerSharedState);
			//YTPlayer.pending.push(this);
			YTPlayer.pending.unshift(this)
			this.addChild(ytBridgeLoader);
			trace("YTPlayer::YTPlayer::fin");
		}
		
		/**
		 * The wrapper SWF has opened a local connection we can talk to
		 */
		internal static function onChannelOpen(localConnectionName:String):void {
			trace("YTPlayer::onChannelOpen::before pop a YTPlayer");
			var player:YTPlayer = pending.pop() as YTPlayer;
			trace("YTPlayer::onChannelOpen::after pop a YTPlayer");
			player.onChannel(localConnectionName);
			YTPlayer.tryLoadNextPlayer();
		}
		
		internal static function tryLoadNextPlayer():void {
			trace("YTPlayer::tryLoadNextPlayer::_ytPlayerSharedState=" + _ytPlayerSharedState);
			if (pending.length > 0) {
				if (_ytPlayerSharedState == FREE) {
					// Load the next waiting swf
					_ytPlayerSharedState == LOCKED;
					pending[pending.length - 1].ytBridgeLoader.load(new URLRequest(YTBRIDGE));
				} else {
					alarmTimer =new Timer(2,1);
					alarmTimer.addEventListener(TimerEvent.TIMER, tryAgainLoadNextPlayer);
					alarmTimer.start();
				}
			} 
			trace("YTPlayer::tryLoadNextPlayer::_ytPlayerSharedState="+_ytPlayerSharedState);
		}
		
		internal static function tryAgainLoadNextPlayer(e:TimerEvent):void {
			YTPlayer.tryLoadNextPlayer();
		}
		
		public static function getSharedState():int {
			return YTPlayer._ytPlayerSharedState;
		}
				
		/**
		 * Set the initial location to start playing at.
		 * Set the seek location before calling cueVideo() or
		 * loadVideo(), or call it during playback to change
		 * the current location.
		 */ 			
		public function getSeek():int {
			return _seekTo;
		}
		
		public function setSeek(s:int):void {
			_seekTo = s;
			_currentTime = s;
			if (_playerState == PLAYING || _playerState == PAUSED)
				sendMsg('seekTo', s);
		}

		/** 
		 * Enable or disable current playback monitoring.
		 * If enabled the propety currentTime will be updated whenever
		 * a video is playing.
		 */
		public function getMonitorPlayback():Boolean {
			return _monitorPlayback;
		}
		
		public function setMonitorPlayback(b:Boolean):void {
			if (b != _monitorPlayback) {
				_monitorPlayback = b;
				checkPlaybackMonitor();
			}
		}

		public function get playerState():int {
			return _playerState;
		}

		/**
		 * Get the duration of playing video. May not be set until 
		 * some time after the video is playing - check the YouTube
		 * documentation.
		 */
		public function getDuration():Number {
			return _duration;
		}

		public function get volume():int {
			return _volume;
		}
		
		public function set volume(v:int):void {
			_volume = Math.min(100, Math.max( v, 0));
			sendMsg( 'setVolume', _volume );
		}
		
		public function get muted():Boolean {
			return _isMuted;
		}
		
		public function set muted(b:Boolean):void {
			_isMuted = b;
			if (_isMuted)
				sendMsg( 'mute' );
			else
				sendMsg( 'unMute' );
		}
		
		public function getCurrentTime():Number {
			return _currentTime;
		}
		
		// For your convenience...
		public function get isPlayable():Boolean {
			return _playerState == CUED || _playerState == PAUSED;
		}
		
		public function get isPauseable():Boolean {
			return _playerState == PLAYING;
		}
		
		public function get playerStateText ():String {
			switch (_playerState){
				case UNLOADED: return "Unloaded";
				case UNSTARTED: return "Unstarted";
				case ENDED: return "Ended";
				case PLAYING: return "Playing";
				case PAUSED: return "Paused";
				case BUFFERING: return "Buffering";
				case CUED: return "Cued";
				default: return "???";
			}
		}

		//---------------------------------------------------------
		// Public Instance Functions
		//---------------------------------------------------------

		/**
		 * Stop playing video
		 */
		public function pauseVideo():void {
			sendMsg('pauseVideo');
		}
		
		/**
		 * Play cued or paused video
		 */
		public function playVideo():void {
			sendMsg('playVideo');
		}
		
		/**
		 * Stops the current video.  The final state will be UNSTARTED.
		 * You cannot resume playing without reloading the video.
		 */ 
		public function stopVideo():void {
			sendMsg('stopVideo');
		}
		
		/**
		 * Clears the video display
		 */
		public function clearVideo():void {
			sendMsg('clearVideo');
		}
		
		/**
		 * Load video but don't play it yet
		 */
		public function cueVideoById(videoId:String):void {
			if (videoId != "") {
				this.videoId = videoId;
			}
			if (_playerState == UNLOADED) {
				alarmTimer =new Timer(2,1);
				alarmTimer.addEventListener(TimerEvent.TIMER, tryAgainCueVideoById);
				alarmTimer.start();
				trace("YTPlayer::cueVideoById::Player non chargé");
			} else {
				trace("YTPlayer::cueVideoById::avant envoi message chargement de la video "+this.videoId);
				sendMsg('cueVideoById', videoId, _seekTo);
				trace("YTPlayer::cueVideoById::après envoi message chargement de la video "+this.videoId);
			}
		}
		
		private function tryAgainCueVideoById(e:TimerEvent):void {
			this.cueVideoById(this.videoId);
		}
		
		/**
		 * Load video and start playing it
		 */
		public function loadVideoById(videoId:String = ""):void {
			if (videoId != "") {
				this.videoId = videoId;
			}
			if (_playerState == UNLOADED) {
				alarmTimer =new Timer(2,1);
				alarmTimer.addEventListener(TimerEvent.TIMER, tryAgainloadVideoById);
				alarmTimer.start();
				trace("YTPlayer::loadVideoById::Player non chargé");
			} else {
				trace("YTPlayer::loadVideoById::avant envoi message chargement de la video "+this.videoId);
				sendMsg('loadVideoById', this.videoId, _seekTo);
				trace("YTPlayer::loadVideoById::après envoi message chargement de la video "+this.videoId);
			}
		}
		
		private function tryAgainloadVideoById(e:TimerEvent):void {
			this.loadVideoById(this.videoId);
		}
		
		/**
		* Must call if you are done with the player!
		*/
		public function dispose():void {
			stopVideo();
			if (_timer && _timer.running)
				_timer.stop();
			sendMsg('dispose');
			inbox.close();
		}
		
		public function setPlayerSize(w:int, h:int):void {
			sendMsg('setSize', w, h);
		}

		//---------------------------------------------------------
		// Implementation
		//---------------------------------------------------------
		private function onInitialize(e:Event):void {
			if (developerKey == null)
				throw new ArgumentError("You MUST supply your YouTube Developer Key!" );
			//outbox.addEventListener(StatusEvent.STATUS,function (event:*):void {trace("AS3 LC status " + event );});
			inbox.client = this;
			//if (1 == pending.unshift( this ))
			//	load();
		}
		
		private function sendMsg(message:String, ...args):void {
			if (outboxName != null)
				outbox.send(outboxName, message, args);
		}
		
		private function checkPlaybackMonitor():void {
			if (_monitorPlayback) {
				if (_playerState == PLAYING) {
					if (_timer == null) { 
						_timer = new Timer(250,0);
						_timer.addEventListener(TimerEvent.TIMER, onTimer);
					}
					_timer.start();
				} else if (_timer != null && _timer.running) {
					_timer.stop();
				}
			} else if (_timer != null && _timer.running) {
				_timer.stop();
			}
		}

		private function onTimer(event:TimerEvent):void {
			// Ask theplayer to tell us the current time
			sendMsg('getCurrentTime');
		}

		//---------------------------------------------------------
		// Callbacks from the wrapper			
		//---------------------------------------------------------			

		/**
		 * The embedded player is loaded and ready to accept commands
		 */
		public function onPlayerLoaded(volume:Number, isMuted:Boolean):void {
			_volume = volume;
			_isMuted = isMuted;
			_playerState = UNSTARTED;
			dispatchEvent(new PlayerStateChangedEvent());
		}


		/**
		 * The wrapper SWF has opened a local connection for us to talk to it
		 */			
		private function onChannel(localConnectionName:String):void {
			outboxName = "YTB" + localConnectionName;
			inbox.connect("YTBHOST" + localConnectionName);
			sendMsg('loadPlayer', developerKey);
		}

		/**
		 * There was a problem loading the wrapper SWF
		 */			
		private function onLoaderIoError(event:IOErrorEvent):void {
			trace('AS3 onLoaderIoError - ' + event);
		}
		
		public function onPlayerStateChange(newState:Number):void {
			_playerState = newState;
			checkPlaybackMonitor();
			// Let's try asking for the duraction once and hope we get it.
			// Its been working so far in tests...
			if (newState == PLAYING)
				sendMsg('getDuration');
			// LSA
			trace("YTPlayer::onPlayerStateChange::_ytPlayerSharedState="+_ytPlayerSharedState);
			if (newState == CUED)
				_ytPlayerSharedState = FREE;
			trace("YTPlayer::onPlayerStateChange::_ytPlayerSharedState="+_ytPlayerSharedState);
			dispatchEvent(new PlayerStateChangedEvent());
		}
		
		public function onPlayerError(errorCode:Number):void {
			trace("AS3 onPlayerError " + errorCode);
		}
		
		public function onVideoDuration(length:Number):void {
			if (length != _duration) {
				_duration = length;
				dispatchEvent(new VideoDurationEvent());
			}
		}
		
		public function onVideoCurrentTime(seconds:Number):void {
			_currentTime = seconds;
			dispatchEvent(new VideoCurrentTimeEvent());
		}


	}
	
}